package com.gitee.wsl.struct.compare.diff.patch

import com.gitee.wsl.struct.compare.diff.PatchFailedException


fun interface ConflictOutput<T> {
    @Throws(PatchFailedException::class)
     fun processConflict(verifyChunk: VerifyChunk, delta: Delta<T>, result: MutableList<T>)
}

 class ExceptionProducingConflictOutput<T> : ConflictOutput<T> {
    override fun processConflict(verifyChunk: VerifyChunk, delta: Delta<T>, result: MutableList<T>) {
        throw PatchFailedException(
            "could not apply patch due to $verifyChunk"
        )
    }
}

 class ConflictProducingConflictOutput : ConflictOutput<String> {
    override fun processConflict(verifyChunk: VerifyChunk, delta: Delta<String>, result: MutableList<String>) {
        if (result.size > delta.source.position) {
            val orgData = mutableListOf<String>()
            repeat(delta.source.size()) { _ ->
                orgData.add(
                    result.removeAt(delta.source.position)
                )
            }
            orgData.add(0, "<<<<<< HEAD")
            orgData.add("======")
            orgData.addAll(delta.source.lines)
            orgData.add(">>>>>>> PATCH")
            result.addAll(delta.source.position, orgData)
        } else {
            TODO("Not supported yet.")
        }
    }
}


/**
 * Describes the patch holding all deltas between the original and revised texts.
 *
 * @param T The type of the compared elements in the 'lines'.
 * @property conflictOutput Alter normal conflict output behaviour to e.g. inclide some conflict statements in the result, like git does it.
 */
 class Patch<T>(
    private var conflictOutput: ConflictOutput<T> = ExceptionProducingConflictOutput(),
) {
     var deltas: MutableList<Delta<T>> = arrayListOf()
        get() {
            field.sortBy { it.source.position }
            return field
        }

    /**
     * Apply this patch to the given target
     *
     * @return the patched text
     */
    @Throws(PatchFailedException::class)
     fun applyTo(target: List<T>): List<T> {
        val result = target.toMutableList()
        val it = deltas.listIterator(deltas.size)
        while (it.hasPrevious()) {
            val delta = it.previous()
            delta.verifyAndApplyTo(result).takeIf { it != VerifyChunk.OK }?.let {
                conflictOutput.processConflict(it, delta, result)
            }
        }
        return result
    }

    /**
     * Restore the text to original. Opposite to applyTo() method.
     *
     * @param target the given target
     * @return the restored text
     */
     fun restore(target: List<T>): List<T> {
        val result = target.toMutableList()
        val it = deltas.listIterator(deltas.size)
        while (it.hasPrevious()) {
            val delta = it.previous()
            delta.restore(result)
        }
        return result
    }

    /**
     * Add the given delta to this patch
     *
     * @param delta the given delta
     */
     fun addDelta(delta: Delta<T>): Boolean = deltas.add(delta)

    override fun toString(): String {
        return "Patch{deltas=$deltas}"
    }

     fun withConflictOutput(conflictOutput: ConflictOutput<T>) {
        this.conflictOutput = conflictOutput
    }

     companion object {
         fun <T> generate(original: List<T>, revised: List<T>, changes: List<Change>): Patch<T> {
            return generate(original, revised, changes, false)
        }

        private fun <T> buildChunk(start: Int, end: Int, data: List<T>): Chunk<T> {
            return Chunk(start, data.subList(start, end))
        }

         fun <T> generate(original: List<T>, revised: List<T>, changes: List<Change>, includeEquals: Boolean): Patch<T> {
            val patch = Patch<T>()
            var startOriginal = 0
            var startRevised = 0
            changes.run {
                if (includeEquals) sortedBy { it.startOriginal } else this
            }.forEach { change ->
                if (includeEquals && startOriginal < change.startOriginal) {
                    patch.addDelta(
                        EqualDelta(
                            buildChunk(startOriginal, change.startOriginal, original),
                            buildChunk(startRevised, change.startRevised, revised)
                        )
                    )
                }
                val orgChunk = buildChunk(change.startOriginal, change.endOriginal, original)
                val revChunk = buildChunk(change.startRevised, change.endRevised, revised)
                when (change.deltaType) {
                    DeltaType.DELETE -> patch.addDelta(DeleteDelta(orgChunk, revChunk))
                    DeltaType.INSERT -> patch.addDelta(InsertDelta(orgChunk, revChunk))
                    DeltaType.CHANGE -> patch.addDelta(ChangeDelta(orgChunk, revChunk))
                    else -> {
                    }
                }
                startOriginal = change.endOriginal
                startRevised = change.endRevised
            }
            if (includeEquals && startOriginal < original.size) {
                patch.addDelta(
                    EqualDelta(
                        buildChunk(startOriginal, original.size, original),
                        buildChunk(startRevised, revised.size, revised)
                    )
                )
            }
            return patch
        }
    }
}